/** @file net_dev.c
  *
  * @brief This file contains the net device function calls
  *
  *  Copyright (C) 2024, Artosyn Technologies. ALL RIGHTS RESERVED.
  *
  *  This Information is proprietary to Artosyn Technologies and MAY NOT
  *  be copied by any method or incorporated into another program without
  *  the express written consent of Artosyn. This Information or any portion
  *  thereof remains the property of Artosyn. The Information contained herein
  *  is believed to be accurate and Artosyn assumes no responsibility or
  *  liability for its use in any way and conveys no license or title under
  *  any patent or copyright and makes no representation or warranty that this
  *  Information is free from patent or copyright infringement.
  *
  */
/****************************
 * Include
 ****************************/
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
#include <net/ip.h>
#include <linux/etherdevice.h>
#include <linux/proc_fs.h>
#include <linux/wait.h>
#include <linux/skbuff.h>
#include <linux/semaphore.h>
#include <linux/rtnetlink.h>

#include "utils/utils.h"
#include "rpc/ar_rpc.h"
#include "control/ar_chardev.h"
#include "oal_main.h"
#include "oal_workqueue.h"
#include "net/net_dev.h"
#include "rpc/session_socket.h"
/*****************************************************************************
 * typedef
 *****************************************************************************/
struct net_device_priv_s {
    struct list_head list;                      /** For list chan */
    void *handle;                               /** Refers to oal_handle */

    /** Kernel info */
    struct net_device_stats stats;              /** Stats of the net device for kernel */
    struct net_device *dev;                     /** Kernel structure net_device */

    u8 netif_id;                                /** ifindex in struct net_device */
    u8 type;                                    /** Port/Vlan, refers to AR_NET_NETIF_T_xxx */
    u16 vlan;                                   /** vlan when type is AR_NET_NETIF_T_VLAN */

    /** Local info */
    ar_socket_session_t *socket_session;        /** AR8030 bb socket session link to net deive */
    u32 bb_socket_flag;                         /** Flags of AR8030 bb socket session */
    bool b_socket_open;                         /** Status of AR8030 bb socket session */

    ar_netif_stats_t ar_stats;                  /** Local stats */

    struct sk_buff_head rx_pkt_list;            /** skb list for rx path */
    struct tasklet_struct task;                 /** task for rx path */
} ;
typedef struct net_device_priv_s net_device_priv_t;

/***************************************************************************
 *declared
 ***************************************************************************/
/** socket rpc buffer size */
#define AR_NET_MTU          (1500)

/*****************************************************************************
 * global variables
 *****************************************************************************/
/** Version of the net device */
static int ar_net_version = VERSION_1DOT0;
/****************************
 * Function definition
 ****************************/
/**
 * @brief Print the packet of the packet
 *
 *  @param data   Data for print
 *  @param len    Length of the data for print
 *
 *  @return       0 for success, otherwise fail
 */
static int
_ar_net_packet_dump(u8* data, u32 len)
{
    u32 cnt = 0;
    char line[256] = {'\0'};
    char tmp[32] = {'\0'};

    if (0 == len)
    {
        return 0;
    }

    for (cnt = 0; cnt < len; cnt++)
    {
        if ((cnt % 16) == 0)
        {
            if (cnt != 0)
            {
                printk("%s", line);
            }

            memset(line, 0, sizeof(line));
            if (cnt == 0)
            {
                sprintf(tmp, "0x%04x:  ", cnt);
            }
            else
            {
                sprintf(tmp, "\n0x%04x:  ", cnt);
            }
            strcat(line, tmp);
        }

        sprintf(tmp, "%02x", data[cnt]);
        strcat(line, tmp);

        if ((cnt % 2) == 1)
        {
            strcat(line, " ");
        }
    }

    printk("%s", line);
    printk("\n");

    return 0;
}

/**
 * @brief Lookup the net_device_priv_t(private) through ar_netif_t(public).
 *
 *  @param handle   Refers to oal_handle.
 *  @param netif  Info of the net device(public)
 *
 *  @return       net_device_priv_t(private), NULL if can't find out.
 */
static net_device_priv_t *
_ar_net_netif_lookup(oal_handle *handle, ar_netif_t *netif)
{
    struct list_head *list;
    net_device_priv_t *priv = NULL;
    unsigned long flags;
    u8 find = 0;

    if (!netif) {
        return NULL;
    }

    if (!handle) {
        return NULL;
    }

    /** Exit if net device is not inited. */
    if (!handle->net_dev_inited) {
        return NULL;
    }

    spin_lock_irqsave(&handle->ar_net_dev_lock, flags);

    list_for_each(list, &handle->ar_net_dev_list)
    {
        if (!list) {
            continue;
        }

        priv = (net_device_priv_t *)list;

        /** Check the bb socket session  */
        if (!priv->socket_session) {
            continue;
        }

        /** Check the type of the device */
        if (netif->type != priv->type)
        {
            continue;
        }

        /** Check the slot of the bb socket */
        if (netif->slot != priv->socket_session->slot)
        {
            continue;
        }

        /** Check the port */
        if ((netif->type == AR_NET_NETIF_T_PORT) &&
            (netif->socket_port == priv->socket_session->port))
        {
            find = 1;
            break;
        }
        else if ((netif->type == AR_NET_NETIF_T_VLAN) &&
            (netif->vlan == priv->vlan))
        {
            find = 1;
            break;
        }
    }
    spin_unlock_irqrestore(&handle->ar_net_dev_lock, flags);

    return find ? priv : NULL;
}

/**
 * @brief Process the rx pkt from bus. Lookup the net_dev through bb socket info
 *  and send to kernel stack.
 *
 *  @param handle   Refers to oal_handle.
 *  @param slot   Slot of the bb socket.
 *  @param type   Type of the net device(PORT/VLAN, refers to AR_NET_NETIF_T_xxx).
 *  @param port   Port of the bb socket.
 *  @param skb    The receiving skb.
 *
 *  @return       0 for success, otherwise fail
 */
static int
_ar_net_packet_rx(oal_handle *handle, u32 slot, u8 type, u32 port, struct sk_buff *skb)
{
    net_device_priv_t *priv = NULL;
    ar_netif_t tmp_netif = {0};
    int ret = 0;

    AR_DBG(DM_NET_DEV, DL_INF, AR_FN_ENTRY_STR);

    tmp_netif.socket_port = port;
    tmp_netif.slot = slot;

    AR_DBG(DM_NET_DEV, DL_VRB, "recieve packet, src slot: %u, src type: %u, src port: %u.\n", slot, type, port);

    tmp_netif.type = type;
    priv = _ar_net_netif_lookup(handle, &tmp_netif);
    if (!priv)
    {
        /** Try to find as vlan */
        AR_DBG(DM_NET_DEV, DL_ERR, "netif can not be found! port %d type %d\n", port, type);
        tmp_netif.type = AR_NET_NETIF_T_VLAN;
        priv = _ar_net_netif_lookup(handle, &tmp_netif);
        if (!priv)
        {
            AR_DBG(DM_NET_DEV, DL_VRB, "netif can not be found!(retry with vlan)\n");
            AR_DBG(DM_NET_DEV, DL_INF, AR_FN_LEAVE_STR);
            return -1;
        }
    }

    priv->stats.rx_packets++;
    priv->stats.rx_bytes += skb->len;

    /** Local stats */
    priv->ar_stats.rx_pkts++;
    priv->ar_stats.rx_bytes += skb->len;

    skb->protocol = eth_type_trans(skb, priv->dev);
    dump_bytes(DL_VRB, "skb to netif", skb->data, skb->len);

    AR_DBG(DM_NET_DEV, DL_VRB, "start netif_rx from dev: %s, ifindex: %d, mtu: %d!\n",
            skb->dev->name, skb->dev->ifindex, skb->dev->mtu);

    /** Send to kernel stack */
    ret = netif_rx(skb);
    if (ret)
    {
        AR_DBG(DM_NET_DEV, DL_VRB,"send packet to network stack error.\n");
    }

    AR_DBG(DM_NET_DEV, DL_INF, AR_FN_LEAVE_STR);

    return 0;
}

/**
 * @brief Get the version of the net device
 * (ioctl API AR_CHARDEV_IOCTL_NET_GET_VERSION)
 *
 *  @param arg      ioctl arg
 *
 *  @return         0 for success, otherwise fail
 */
static int
linux_get_ar_net_version(unsigned long arg)
{
    if (copy_to_user((int*)arg, (void*)&ar_net_version, sizeof(ar_net_version)))
    {
        return -EFAULT;
    }

    return 0;
}

/**
 * @brief Open the net device
 * (net dev API ndo_open)
 *
 *  @param dev      struct net_device in kernel
 *
 *  @return         0 for success, otherwise fail
 */
static int
ar_net_device_open(struct net_device *dev)
{
    netif_carrier_on(dev);

    netif_start_queue(dev);

    return 0;
}

/**
 * @brief Stop the net device(net dev API ndo_stop)
 *
 *  @param dev      struct net_device in kernel
 *
 *  @return         0 for success, otherwise fail
 */
static int
ar_net_device_stop(struct net_device *dev)
{
    netif_stop_queue(dev);

    return 0;
}

/**
 * @brief Send the skb from kernel stack
 * (net dev API ndo_start_xmit)
 * tx path: kernel->net_dev->ar_rpc->bus
 *
 *  @param skb      skb from kernel stack
 *  @param dev      struct net_device in kernel
 *
 *  @return         0 for success, otherwise fail
 */
static netdev_tx_t
ar_net_device_tx(struct sk_buff *skb, struct net_device *dev)
{
    net_device_priv_t *priv = NULL;
    int ret = 0;

    if (!dev) {
        return NETDEV_TX_BUSY;
    }

    priv = netdev_priv(dev);

    priv->stats.tx_packets++;
    priv->stats.tx_bytes += skb->len;

    AR_DBG(DM_NET_DEV, DL_VRB, "b_socket_open = %d\n", priv->b_socket_open);

    if (priv->b_socket_open) {
        if (priv->handle) {
            if (priv->socket_session) {
                /** Send skb to rpc */
                ret = ar_rpc_socket_tx(priv->handle,
                                        priv->socket_session->slot,
                                        priv->socket_session->port,
                                        skb,
                                        (priv->bb_socket_flag & BB_SOCK_FLAG_DATAGRAM),
                                        (void*)priv);
                if (ret < 0)
                {
                    AR_DBG(DM_NET_DEV, DL_VRB, "write socket(slot %d port %d) fail, ret = %d.\n",
                                priv->socket_session->slot, priv->socket_session->port, ret);
                    ret = -1;
                    goto fail;
                }
                else {
                    ret = 0;
                }
            }
            else {
                AR_DBG(DM_NET_DEV, DL_ERR, "socket session is NULL! Unable to operate socket!\n");
                ret = -2;
                goto fail;
            }
        }
        else {
            AR_DBG(DM_NET_DEV, DL_ERR, "handle is NULL! Unable to operate socket!\n");
            ret = -3;
            goto fail;
        }
    }
    else {
        ret = -4;
        goto fail;
    }

    return NETDEV_TX_OK;

fail:
    // netif_stop_queue(dev);
    // return NETDEV_TX_BUSY;
    dev_kfree_skb_any(skb);
    return NETDEV_TX_OK;
}

/**
 * @brief Process after tx done and wake the netif queue.
 *
 *  @param poal_handle  Point to oal_handle
 *  @param req          skbreq has been tx done
 *
 *  @return             N/A
 */
void
ar_net_device_tx_cb(oal_handle *poal_handle, struct ar_skbreq *req)
{
    net_device_priv_t *priv = NULL;
    unsigned long flags;

    AR_DBG(DM_GENERIC, DL_INF, "%s\n", __func__);

    if (!req) {
        return;
    }
    priv = (net_device_priv_t *)req->priv_data;

    if (!priv) {
        return;
    }

    if (!poal_handle) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: handle is NULL!\n", __func__);
        return;
    }

    spin_lock_irqsave(&poal_handle->qlock, flags);

    // if (list_empty(&poal_handle->pktout_postq)) {
        netif_wake_queue(priv->dev);
    // }

    spin_unlock_irqrestore(&poal_handle->qlock, flags);
}

/**
 * @brief Process the tx timeout
 * (net dev API ndo_tx_timeout)
 *
 *  @param dev      struct net_device in kernel
 *
 *  @return         N/A
 */
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0)
static void
ar_net_tx_timeout(struct net_device *dev)
#else
static void
ar_net_tx_timeout(struct net_device *dev, unsigned int txqueue)
#endif
{
    net_device_priv_t *priv = netdev_priv(dev);

    AR_DBG(DM_NET_DEV, DL_INF, "Tx timeout: calling netif_wake_queue for %s\n", dev->name);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)
    netif_trans_update(dev); /* prevent tx timeout */
#else
    struct netdev_queue *txq = netdev_get_tx_queue(dev, 0);
    txq_trans_update(txq);
#endif
    netif_wake_queue(dev);
}

/**
 * @brief Get network device statistics.(net dev API ndo_get_stats)
 *
 *  @param dev      struct net_device in kernel
 *
 *  @return         N/A
 */
static struct net_device_stats *
ar_net_device_get_stats(struct net_device *dev)
{
    net_device_priv_t *priv = netdev_priv(dev);

    return &priv->stats;
}

/**
 * @brief  This function is called when the Media Access Control address
 *    needs to be changed. If this interface is not defined, the
 *    mac address can not be changed
 * (net dev API ndo_set_mac_address)
 *
 *  @param dev      struct net_device in kernel
 *  @param p        mac address refers to struct sockaddr
 *
 *  @return         0 for success, otherwise fail
 */
static int ar_net_device_set_mac_address(struct net_device *dev, void *p)
{
    int ret = 0;
    struct sockaddr *addr = p;

    if (!is_valid_ether_addr(addr->sa_data))
        return -EADDRNOTAVAIL;

    if (netif_running(dev))
        return -EBUSY;

#if KERNEL_VERSION(5, 16, 0) <= LINUX_VERSION_CODE
    eth_hw_addr_set(dev, addr->sa_data);
#else
    memcpy(dev->dev_addr, addr->sa_data, ETH_ALEN);
#endif

    return ret;
}

/**
 * @brief  This function is called when the Maximum Transmission Unit
 *    needs to be changed.
 * (net dev API ndo_change_mtu)
 *
 *  @param dev      struct net_device in kernel
 *  @param new_mtu  MTU to set
 *
 *  @return         0 for success, otherwise fail
 */
static int
ar_net_device_change_mtu(struct net_device *dev, int new_mtu)
{
    if (new_mtu < 68 /*|| new_mtu > 1500*/) {
        return -EINVAL;
    }
    dev->mtu = new_mtu;
    return 0;
}

/**
 * @brief  ioctl function for net devie.
 * (net dev API ndo_do_ioctl)
 *
 *  @param dev      struct net_device in kernel
 *  @param ifr      ioctl ifr
 *  @param cmd      ioctl cmd
 *
 *  @return         0 for success, otherwise fail
 */
static int ar_net_device_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
    AR_DBG(DM_NET_DEV, DL_MSG, AR_FN_ENTRY_STR);
    return 0;
}

/** Operation functions for net device */
static const struct net_device_ops ar_net_netdev_ops = {
    .ndo_open               = ar_net_device_open,
    .ndo_stop               = ar_net_device_stop,
    .ndo_start_xmit         = ar_net_device_tx,
    .ndo_tx_timeout         = ar_net_tx_timeout,
    .ndo_get_stats          = ar_net_device_get_stats,
    .ndo_validate_addr      = NULL,
    .ndo_set_rx_mode        = NULL,
    .ndo_set_mac_address    = ar_net_device_set_mac_address,
    .ndo_do_ioctl           = ar_net_device_ioctl,
    .ndo_change_mtu         = ar_net_device_change_mtu,
};

/**
 * @brief  Init the param of the net device
 *
 *  @param dev      struct net_device in kernel
 *
 *  @return         N/A
 */
static void ar_netdev_setup(struct net_device *dev)
{
    ether_setup(dev);
    dev->priv_flags &= ~IFF_TX_SKB_SHARING;

#if LINUX_VERSION_CODE <  KERNEL_VERSION(4, 12, 0)
    dev->destructor = free_netdev;
#else
    dev->needs_free_netdev = true;
#endif
    dev->watchdog_timeo = AR_NET_TX_LIFETIME_MS;
    dev->needed_headroom = AR_NET_TX_MAX_HEADROOM;
    dev->needed_tailroom = AR_NET_TX_MAX_TAILROOM;

    dev->hw_features = 0;
}

/**
 * @brief  Init the net device param and set mac/name.
 *
 *  @param mac      mac of the net device, in byte mode.
 *  @param name     Name of the net device.
 *
 *  @return         net_device that created. Return NULL if failed.
 */
static struct net_device* ar_net_init_dev(u8 *mac, char *name)
{
    struct net_device *dev;

    /** Create Ethernet device */
    dev = alloc_etherdev(sizeof(net_device_priv_t));

    if (dev == NULL) {
        AR_ERROR("Error allocating Ethernet device.\n");
        return NULL;
    }

#ifdef SET_MODULE_OWNER
    SET_MODULE_OWNER(dev);
#endif

    /** Fill the default net device cfg */
    ar_netdev_setup(dev);

    /** Set the device MAC address */
#if KERNEL_VERSION(5, 16, 0) <= LINUX_VERSION_CODE
    eth_hw_addr_set(dev, mac);
#else
    memcpy(dev->dev_addr, mac, 6);
#endif

    /** Device information -- not available right now */
    dev->irq = 0;
    dev->base_addr = 0;

    /** Default MTU should not exceed MTU of switch front-panel ports */
    dev->mtu = AR_NET_MTU;

    /** Device vectors */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29))
    dev->netdev_ops = &ar_net_netdev_ops;
#else
    dev->open = ar_net_device_open;
    dev->hard_start_xmit = ar_net_device_tx;
    dev->stop = ar_net_device_stop;
    dev->get_stats = ar_net_device_get_stats;
    dev->change_mtu = ar_net_device_change_mtu;
#endif

    if (name && *name)
    {
        strncpy(dev->name, name, IFNAMSIZ-1);
    }

    /** Register the kernel Ethernet device */
    if (register_netdev(dev))
    {
        AR_ERROR("Error registering Ethernet device.");
        free_netdev(dev);
        return NULL;
    }

    AR_DBG(DM_NET_DEV, DL_MSG, "Created Ethernet device %s, ifindex: %d.\n", dev->name, dev->ifindex);

    return dev;
}

/**
 * @brief  Take pkt from bus and ready sending to kernel stack.
 *
 *  @param data     Refers to net_device_priv_t.
 *
 *  @return         N/A
 */
static void ar_net_task(unsigned long data)
{
    net_device_priv_t *priv = (net_device_priv_t *)data;
    oal_handle *handle = (oal_handle *)priv->handle;
    struct sk_buff *skb;

    AR_DBG(DM_NET_DEV, DL_INF, AR_FN_ENTRY_STR);

    if (!priv) {
        return;
    }

#ifndef NAPI_SUPPORT
    /** handle rx packet */
    while ((skb = skb_dequeue(&priv->rx_pkt_list)) != NULL) {
        if (!priv->socket_session) {
            break;
        }
        _ar_net_packet_rx(handle, priv->socket_session->slot, priv->type, priv->socket_session->port, skb);
    }
#endif

    AR_DBG(DM_NET_DEV, DL_INF, AR_FN_LEAVE_STR);
}

/**
 * @brief  Create the net device and open the bb socket.
 *          (ioctl API for AR_CHARDEV_IOCTL_NET_HANDLE_NETIF->AR_NET_OP_CREATE)
 *
 *  @param handle   Refers to oal_handle.
 *  @param netif    Info of the net device(public)
 *
 *  @return         0 for success, otherwise fail
 */
static int ar_net_netif_create(oal_handle *handle, ar_netif_t *netif)
{
    struct net_device *dev;
    struct list_head *list;
    net_device_priv_t *priv, *cur_priv;
    char mac_0[6] = {0};
    unsigned long flags;
    int found = 0;
    int ret = 0;

    if (!handle->net_dev_inited) {
        AR_DBG(DM_NET_DEV, DL_ERR, "%s %d net_dev_inited is not inited!\n", __FUNCTION__, __LINE__);
        return -1;
    }

    /** Check the type of the port */
    if ((netif->type != AR_NET_NETIF_T_VLAN) && (netif->type != AR_NET_NETIF_T_PORT))
    {
        AR_DBG(DM_NET_DEV, DL_ERR, "%s %d netif->type %d is invalid\n", __FUNCTION__, __LINE__, netif->type);
        return -2;
    }

    /** Init the mac address */
    if (!memcmp(netif->mac, mac_0, 6))
    {
        AR_DBG(DM_NET_DEV, DL_ERR, "%s %d mac is zero!\n", __FUNCTION__, __LINE__);
        return -4;
    }

    /** Get the net_device_priv_t from ar_netif_t's private data */
    priv = _ar_net_netif_lookup(handle, netif);
    if (priv)
    {
        AR_DBG(DM_NET_DEV, DL_ERR, "%s %d _ar_net_netif_lookup failed\n", __FUNCTION__, __LINE__);
        return -5;
    }

    /** Set param and register_netdev */
    if ((dev = ar_net_init_dev(netif->mac, netif->name)) == NULL)
    {
        AR_DBG(DM_NET_DEV, DL_ERR, "%s %d ar_net_init_dev failed\n", __FUNCTION__, __LINE__);
        return -6;
    }

    priv = netdev_priv(dev);
    priv->dev = dev;
    priv->type = netif->type;
    priv->vlan = (netif->type == AR_NET_NETIF_T_VLAN) ? netif->vlan : 0;
    priv->b_socket_open = false;

    priv->socket_session = create_socket_session(handle, netif->slot, netif->socket_port, BB_SOCK_FLAG_TX | BB_SOCK_FLAG_RX | BB_SOCK_FLAG_DATAGRAM);
    if (!priv->socket_session) {
        /** TBD: deinit net dev */
        return -6;
    }

    priv->socket_session->tx_buf_size = netif->tx_buf_size;
    priv->socket_session->rx_buf_size = netif->rx_buf_size;

    /** Clear the stats */
    memset(&priv->ar_stats, 0, sizeof(ar_netif_stats_t));

    tasklet_init(&priv->task, ar_net_task, (unsigned long)priv);
    skb_queue_head_init(&priv->rx_pkt_list);

    spin_lock_irqsave(&handle->ar_net_dev_lock, flags);

    /*
     * We insert network interfaces sorted by ID.
     * In case an interface is destroyed, we reuse the ID
     * the next time an interface is created.
     */
    list_for_each(list, &handle->ar_net_dev_list)
    {
        cur_priv = (net_device_priv_t *)list;
        if (dev->ifindex < cur_priv->netif_id)
        {
            found = 1;
            break;
        }
    }
    priv->netif_id = dev->ifindex;
    if (found)
    {
        /** Replace previously removed interface */
        list_add_tail(&priv->list, &cur_priv->list);
    }
    else
    {
        /** No holes - add to end of list */
        list_add_tail(&priv->list, &handle->ar_net_dev_list);
    }

    spin_unlock_irqrestore(&handle->ar_net_dev_lock, flags);

    AR_DBG(DM_NET_DEV, DL_MSG, "Assigned ID %d to Ethernet device %s, mac: %x%x.%x%x.%x%x\n", priv->netif_id, dev->name,
        dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2], dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]);

    netif->netif_id = priv->netif_id;

    rtnl_lock();
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)
    ret = dev_open(dev, NULL);
#else
    ret = dev_open(dev);
#endif
    if (ret)
    {
        AR_DBG(DM_NET_DEV, DL_ERR, "device %s start fail, ret = %d.\n", dev->name, ret);
        return -6;
    }

    rtnl_unlock();

    /** Call ar_rpc API to open bb socket */
    if (handle) {
        priv->bb_socket_flag = BB_SOCK_FLAG_TX | BB_SOCK_FLAG_RX | BB_SOCK_FLAG_DATAGRAM;
        ret = ar_bb_rpc_socket_open(handle,
                                    priv->socket_session->slot,
                                    priv->socket_session->port,
                                    priv->socket_session->flags,
                                    priv->socket_session->tx_buf_size,
                                    priv->socket_session->rx_buf_size);
        if (ret)
        {
            AR_DBG(DM_NET_DEV, DL_ERR, "open socket(slot %d port %d tx_buf:%d rx_buf:%d) fail, ret = %d.\n",
                        priv->socket_session->slot,
                        priv->socket_session->port,
                        priv->socket_session->tx_buf_size,
                        priv->socket_session->rx_buf_size, ret);
            return -7;
        }
        AR_DBG(DM_NET_DEV, DL_MSG, "Open socket success!\n");
        priv->b_socket_open = true;
        priv->handle = (void*)handle;
    }
    else {
        AR_DBG(DM_NET_DEV, DL_ERR, "handle is NULL! Unable to open socket!\n");
        return -8;
    }

    return 0;
}

/**
 * @brief  Free the net device and close the bb socket.
 *
 *  @param priv     net_device_priv_t, private structure of net device
 *
 *  @return         0 for success, otherwise fail
 */
static int ar_net_dev_free(net_device_priv_t *priv)
{
    int ret = 0;
    struct net_device *dev = NULL;
    oal_handle *handle = NULL;

    if (!priv) {
        return -EINVAL;
    }

    dev = (struct net_device *)priv->dev;
    if (!dev) {
        return -EINVAL;
    }

    handle = (oal_handle *)priv->handle;
    if (!handle) {
        return -EINVAL;
    }

    netdev_info(dev, "Remove Interface\n");

    /** Close the socket */
    ret = ar_rpc_socket_close(handle,
                            priv->socket_session->slot,
                            priv->socket_session->port,
                            priv->socket_session->flags);
    if (ret)
    {
        AR_DBG(DM_NET_DEV, DL_ERR, "close socket(slot %d port %d flags 0x%x) fail, ret = %d.\n",
                    priv->socket_session->slot,
                    priv->socket_session->port,
                    priv->socket_session->flags,
                    ret);
        return -4;
    }

    remove_socket_session(handle, priv->socket_session);

    if (dev->reg_state == NETREG_REGISTERED) {
        AR_DBG(DM_NET_DEV, DL_MSG, "unregister dev %s.\n", dev->name);
        unregister_netdev(dev);
    }

    return ret;
}

/**
 * @brief  Destroy the net device.
 *          (ioctl API for AR_CHARDEV_IOCTL_NET_HANDLE_NETIF->AR_NET_OP_DESTORY)
 *
 *  @param handle   Refers to oal_handle.
 *  @param netif    Info of the net device(public)
 *
 *  @return         0 for success, otherwise fail
 */
static int ar_net_netif_destroy(oal_handle *handle, ar_netif_t *netif)
{
    int ret = 0;
    struct net_device *dev;
    net_device_priv_t *priv = NULL;
    unsigned long flags;

    if (!handle->net_dev_inited) {
        return -1;
    }

    priv = _ar_net_netif_lookup(handle, netif);
    if (!priv)
    {
        AR_DBG(DM_NET_DEV, DL_ERR, "%s priv is NULL!\n", __FUNCTION__);
        return -2;
    }

    spin_lock_irqsave(&handle->ar_net_dev_lock, flags);
    list_del(&priv->list);
    spin_unlock_irqrestore(&handle->ar_net_dev_lock, flags);

    dev = priv->dev;
    AR_DBG(DM_NET_DEV, DL_MSG, "Removing virtual Ethernet device %s, netif_id: %d.\n", dev->name, priv->netif_id);

    ret = ar_net_dev_free(priv);
    if (ret != 0) {
        AR_DBG(DM_NET_DEV, DL_ERR, "%s ar_net_dev_free failed! ret %d\n", __FUNCTION__, ret);
    }

    return ret;
}

/**
 * @brief  Resize the buffer of the net device.
 *          (ioctl API for AR_CHARDEV_IOCTL_NET_HANDLE_NETIF->AR_NET_OP_BUF_RESIZE)
 *
 *  @param handle   Refers to oal_handle.
 *  @param netif    Info of the net device(public)
 *
 *  @return         0 for success, otherwise fail
 */
static int ar_net_netif_buf_resize(oal_handle *handle, ar_netif_t *netif)
{
    int ret = 0;
    struct net_device *dev;
    net_device_priv_t *priv = NULL;
    unsigned long flags;

    if (!handle->net_dev_inited) {
        return -1;
    }

    priv = _ar_net_netif_lookup(handle, netif);
    if (!priv)
    {
        AR_DBG(DM_NET_DEV, DL_ERR, "%s priv is NULL!\n", __FUNCTION__);
        return -2;
    }

    dev = priv->dev;
    AR_DBG(DM_NET_DEV, DL_MSG, "Resize virtual Ethernet device %s, netif_id: %d. rx(%d->%ld) tx(%d->%ld)\n",
                        dev->name,
                        priv->netif_id,
                        priv->socket_session->rx_buf_size, netif->rx_buf_size,
                        priv->socket_session->tx_buf_size, netif->tx_buf_size);

    netdev_info(dev, "Resize net dev %s\n", dev->name);

    /** Close the socket */
    ret = ar_rpc_socket_close(handle,
                            priv->socket_session->slot,
                            priv->socket_session->port,
                            priv->socket_session->flags);
    if (ret)
    {
        AR_DBG(DM_NET_DEV, DL_ERR, "close socket(slot %d port %d flags 0x%x) fail, ret = %d.\n",
                    priv->socket_session->slot,
                    priv->socket_session->port,
                    priv->socket_session->flags,
                    ret);
        return -3;
    }
    priv->b_socket_open = false;

    priv->socket_session->tx_buf_size = netif->tx_buf_size;
    priv->socket_session->rx_buf_size = netif->rx_buf_size;
    priv->bb_socket_flag = BB_SOCK_FLAG_TX | BB_SOCK_FLAG_RX | BB_SOCK_FLAG_DATAGRAM;
    ret = ar_bb_rpc_socket_open(handle,
                                priv->socket_session->slot,
                                priv->socket_session->port,
                                priv->socket_session->flags,
                                priv->socket_session->tx_buf_size,
                                priv->socket_session->rx_buf_size);
    if (ret)
    {
        AR_DBG(DM_NET_DEV, DL_ERR, "open socket(slot %d port %d tx_buf:%d rx_buf:%d) fail, ret = %d.\n",
                    priv->socket_session->slot,
                    priv->socket_session->port,
                    priv->socket_session->tx_buf_size,
                    priv->socket_session->rx_buf_size, ret);
        return -7;
    }
    AR_DBG(DM_NET_DEV, DL_MSG, "Open socket success!\n");
    priv->b_socket_open = true;

    return ret;
}

/**
 * @brief  Get the info of the net device.
 *          (ioctl API for AR_CHARDEV_IOCTL_NET_HANDLE_NETIF->AR_NET_OP_GET)
 *
 *  @param handle   Refers to oal_handle.
 *  @param netif    Info of the net device(public)
 *
 *  @return         0 for success, otherwise fail
 */
static int
ar_net_netif_get(oal_handle *handle, ar_netif_t *netif)
{
    net_device_priv_t *priv = NULL;

    if (!handle->net_dev_inited) {
        return -1;
    }

    if (!netif)
    {
        return -2;
    }

    priv = _ar_net_netif_lookup(handle, netif);
    if (!priv)
    {
        return -3;
    }

    netif->netif_id = priv->netif_id;
    memcpy(netif->mac, priv->dev->dev_addr, 6);
    memcpy(netif->name, priv->dev->name, AR_NET_MAX_NET_NAME_LEN - 1);

    return 0;
}

/**
 * @brief  Hndle the net device ioctl.
 *          (ioctl API for AR_CHARDEV_IOCTL_NET_HANDLE_NETIF)
 *
 *  @param handle   Refers to oal_handle.
 *  @param arg      arg copy from user
 *
 *  @return         0 for success, otherwise fail
 */
static int
linux_handle_netif(oal_handle *handle, unsigned long arg)
{
    ar_netif_t netif = {0};
    int ret = 0;

    if (!handle->net_dev_inited) {
        return -EFAULT;
    }

    if (copy_from_user(&netif, (void*)arg, sizeof(ar_netif_t)))
    {
        return -EFAULT;
    }

    switch (netif.op_type)
    {
        case AR_NET_OP_CREATE:
            ret = ar_net_netif_create(handle, &netif);
            break;
        case AR_NET_OP_DESTORY:
            ret = ar_net_netif_destroy(handle, &netif);
            break;
        case AR_NET_OP_GET:
            ret = ar_net_netif_get(handle, &netif);
            break;
        case AR_NET_OP_BUF_RESIZE:
            ret = ar_net_netif_buf_resize(handle, &netif);
            break;
        default:
            break;
    }

    if ((netif.op_type == AR_NET_OP_CREATE) ||
        (netif.op_type == AR_NET_OP_GET))
    {
        if (copy_to_user((void*)arg, &netif, sizeof (ar_netif_t)))
        {
            return -EFAULT;
        }
    }

    return ret;
}

/**
 *  @brief This function handles the wrapper_dev ioctl
 *
 *  @param m_dev    A pointer to wrapper_dev structure
 *  @param cmd      Cmd of mdev
 *  @param arg      Arg of mdev
 *
 *  @return         0 for success, otherwise fail
 */
static int
net_mdev_ioctl(struct m_dev *m_dev, unsigned int cmd, unsigned long arg)
{
    int ret = 0;
    unsigned long flags;
    oal_handle *handle = NULL;

    AR_DBG(DM_NET_DEV, DL_INF, AR_FN_ENTRY_STR);


    if (!m_dev) {
        AR_DBG(DM_NET_DEV, DL_ERR,"Ioctl for unknown device (m_dev=NULL)\n");
        AR_DBG(DM_NET_DEV, DL_INF, AR_FN_LEAVE_STR);
        return -ENODEV;
    }

    handle = (oal_handle*)m_dev->handle;
    if (!handle) {
        AR_DBG(DM_NET_DEV, DL_ERR,"Ioctl for unknown handle (m_dev->handle=NULL)\n");
        AR_DBG(DM_NET_DEV, DL_INF, AR_FN_LEAVE_STR);
        return -ENODEV;
    }

    if (!test_bit(CHAR_DEV_RUNNING, &m_dev->flags)) {
        AR_DBG(DM_NET_DEV, DL_ERR,"CHAR_DEV_RUNNING not set, flag=0x%lx\n",
               m_dev->flags);
        return -EBUSY;
    }

    spin_lock_irqsave(&handle->net_dev_opr_lock, flags);
    if (!handle->net_dev_inited) {
        AR_DBG(DM_NET_DEV, DL_ERR, "Ioctl net dev not inited!\n");
        spin_unlock_irqrestore(&handle->net_dev_opr_lock, flags);
        return -EFAULT;
    }
    atomic_inc(&handle->net_dev_opr_ref_cnt);
    spin_unlock_irqrestore(&handle->net_dev_opr_lock, flags);

    AR_DBG(DM_NET_DEV, DL_MSG, "IOCTL: cmd=0x%x\n", cmd);

    switch (cmd)
    {
        case AR_CHARDEV_IOCTL_NET_GET_VERSION:
            ret = linux_get_ar_net_version(arg);
            break;
        case AR_CHARDEV_IOCTL_NET_HANDLE_NETIF:
            ret = linux_handle_netif(handle, arg);
            break;
        default:
            break;
    }

    atomic_dec(&handle->net_dev_opr_ref_cnt);

    AR_DBG(DM_NET_DEV, DL_INF, AR_FN_LEAVE_STR);
    return ret;
}

/**
 *  @brief This function closes the wrapper device
 *
 *  @param m_dev    A pointer to m_dev structure
 *
 *  @return         0 for success, otherwise fail
 */
static int
net_mdev_close(struct m_dev *m_dev)
{
    AR_DBG(DM_NET_DEV, DL_INF, AR_FN_ENTRY_STR);

    if (!test_and_clear_bit(CHAR_DEV_UP, &m_dev->flags)) {
        AR_DBG(DM_NET_DEV, DL_INF, AR_FN_LEAVE_STR);
        return 0;
    }
    /** wait up pending read and unregister char dev */
    wake_up_interruptible(&m_dev->req_wait_q);

    /** Drop queues */
    skb_queue_purge(&m_dev->rx_q);

    if (!test_and_clear_bit(CHAR_DEV_RUNNING, &m_dev->flags)) {
        AR_DBG(DM_NET_DEV, DL_INF, AR_FN_LEAVE_STR);
        return 0;
    }

    module_put(THIS_MODULE);

    m_dev->flags = 0;

    AR_DBG(DM_NET_DEV, DL_INF, AR_FN_LEAVE_STR);

    return 0;
}

/**
 *  @brief This function opens the wrapper device
 *
 *  @param m_dev   A pointer to m_dev structure
 *
 *  @return        0 for success, otherwise fail
 */
static int
net_mdev_open(struct m_dev *m_dev)
{
    AR_DBG(DM_NET_DEV, DL_INF, AR_FN_ENTRY_STR);

    if (try_module_get(THIS_MODULE) == 0)
        return OAL_STATUS_FAILURE;

    set_bit(CHAR_DEV_RUNNING, &m_dev->flags);

    AR_DBG(DM_NET_DEV, DL_INF, AR_FN_LEAVE_STR);

    return OAL_STATUS_SUCCESS;
}

/**
 *  @brief clean up m_devs
 *
 *  @param poal_handle  Point to oal_handle
 *
 *  @return    N/A
 */
static void
net_mdev_clean_up(oal_handle *poal_handle)
{
    struct m_dev *m_dev = NULL;

    AR_DBG(DM_NET_DEV, DL_INF, AR_FN_ENTRY_STR);

    if (!poal_handle) {
        AR_DBG(DM_NET_DEV, DL_ERR, "%s poal_handle is NULL!\n", __FUNCTION__);
        return;
    }

    m_dev = &(poal_handle->net_mdev);
    AR_DBG(DM_NET_DEV, DL_MSG, "Delete %s\n", m_dev->name);

    if(mdev_req_lock(m_dev)) {
		return;
    }

    m_dev->close(m_dev);

    /**  unregister m_dev to char_dev */
    if (poal_handle->chardev_class) {
        ar_chardev_cleanup_one(m_dev, poal_handle->chardev_class);
    }

    mdev_req_unlock(m_dev);

    AR_DBG(DM_NET_DEV, DL_INF, AR_FN_LEAVE_STR);
    return;
}

/**
 *  @brief Init the net device module
 *
 *  @param handle  Point to oal_handle
 *
 *  @return        0 for success, otherwise fail
 */
int
ar_net_dev_init(oal_handle *handle)
{
    int ret = 0;
    unsigned long flags;
    struct ar_cdev *char_dev = NULL;
    struct m_dev *net_mdev = NULL;
    char dev_file[DEV_NAME_LEN + 5];
    char mod_name[AR_MODULE_NAME_LEN] = {0};

    if (!handle) {
        return -EINVAL;
    }

    handle->net_dev = (void*)&handle->ar_net_dev_list;

    INIT_LIST_HEAD(&handle->ar_net_dev_list);
    spin_lock_init(&handle->ar_net_dev_lock);
    spin_lock_init(&handle->net_dev_opr_lock);
    atomic_set(&handle->net_dev_opr_ref_cnt, 0);

    /** Init the net_mdev */
    skb_queue_head_init(&handle->net_mdev.rx_q);
    init_waitqueue_head(&handle->net_mdev.req_wait_q);

    sema_init(&handle->net_mdev.req_lock, 1);
	spin_lock_init(&handle->net_mdev.lock);
    mutex_init(&handle->net_mdev.rlock);

    handle->net_mdev.open = net_mdev_open;
    handle->net_mdev.close = net_mdev_close;
    handle->net_mdev.ioctl = net_mdev_ioctl;

    /** alloc char dev node */
    char_dev = ar_alloc_char_dev();
    if (!char_dev) {
        ret = -ENOMEM;
        goto done;
    }

    /** Fill the param of the char device */
    char_dev->minor = NET_CHAR_MINOR_BASE + handle->handle_idx;
    char_dev->dev_type = CHARDEV_TYPE_NET;

    /** Set the name of the char device */
    if (handle->net_mdev_name) {
        snprintf(handle->net_mdev.name, sizeof(handle->net_mdev.name), "%s%d", handle->net_mdev_name, handle->handle_idx);
    }
    else {
        snprintf(handle->net_mdev.name, sizeof(handle->net_mdev.name), "%s%d", NET_DEV_CDEV_NAME, handle->handle_idx);
    }

    snprintf(dev_file, sizeof(dev_file), "/dev/%s", handle->net_mdev.name);

    /** register m_dev to char device */
    handle->net_mdev.index = char_dev->minor;
    handle->net_mdev.handle = (void*)handle;
    char_dev->m_dev = &(handle->net_mdev);

    /** create char device node */
    sprintf(mod_name, "%s%d", AR_MODULE_NAME, handle->handle_idx);

    /** create char device node */
    ar_register_char_dev(char_dev, handle->chardev_class, mod_name, handle->net_mdev.name);

#if defined(CDEV_SET_PERMISSION)
    /** chmod & chown for char device */
    ar_chardev_chmod(dev_file, 0666);
#endif

    AR_DBG(DM_NET_DEV, DL_MSG, "Create net chardev %s Done.\n", dev_file);

    spin_lock_irqsave(&handle->net_dev_opr_lock, flags);

    handle->net_dev_inited = true;

    spin_unlock_irqrestore(&handle->net_dev_opr_lock, flags);

    AR_DBG(DM_NET_DEV, DL_MSG, "Register net drv device!\n");

done:
    return ret;
}

/**
 *  @brief Deinit the net device module
 *
 *  @param handle  Point to oal_handle
 *
 *  @return        N/A
 */
void
ar_net_dev_exit(oal_handle *handle)
{
    int ret = 0;
    net_device_priv_t *priv, *next;
    unsigned long flags;
    u8 slot = 0;
    u8 chan_id = 0;

    AR_DBG(DM_NET_DEV, DL_MSG, "start unregister net drv device!\n");

    if (!handle) {
        AR_DBG(DM_NET_DEV, DL_ERR, "%s handle is NULL!\n", __FUNCTION__);
        return;
    }

    /** Remove char dev */
    net_mdev_clean_up(handle);
    AR_DBG(DM_NET_DEV, DL_MSG, "net mdev removed done.\n");

    spin_lock_irqsave(&handle->net_dev_opr_lock, flags);

    if (!handle->net_dev_inited) {
        spin_unlock_irqrestore(&handle->net_dev_opr_lock, flags);
        return;
    }

    AR_DBG(DM_NET_DEV, DL_MSG, "destory all netif!\n");

    handle->net_dev_inited = false;

    spin_unlock_irqrestore(&handle->net_dev_opr_lock, flags);

    // check the atomic cnt
    while (atomic_read(&handle->net_dev_opr_ref_cnt) != 0) {
        cpu_relax();  // relax cpu for less cost
    }

    list_for_each_entry_safe(priv, next, &handle->ar_net_dev_list, list)
    {
        if (priv) {
            /** Close the bb socket */
            ret = ar_net_dev_free(priv);
            if (ret != 0) {
                AR_DBG(DM_NET_DEV, DL_ERR, "%s ar_net_dev_free failed! ret %d\n", __FUNCTION__, ret);
            }
            list_del_init(&priv->list);
        }
    }

    AR_DBG(DM_NET_DEV, DL_MSG, "Unregister net drv device!\n");
}

/**
 *  @brief Push the skb to the list for net device rx
 *
 *  @param handle   Refers to oal_handle.
 *  @param netif    Info of the net device(public)
 *  @param skb      skb receive from bus
 *
 *  @return         0 for success, otherwise fail
 */
int
ar_net_rx_enqueue(oal_handle *handle, ar_netif_t* netif, struct sk_buff *skb)
{
    int ret = 0;
    unsigned long flags;
    net_device_priv_t *priv = NULL;

    AR_DBG(DM_NET_DEV, DL_INF, AR_FN_ENTRY_STR);

    if (!handle) {
        AR_DBG(DM_NET_DEV, DL_INF, AR_FN_LEAVE_STR);
        ret = -1;
        goto exit;
    }

    spin_lock_irqsave(&handle->net_dev_opr_lock, flags);
    if (!handle->net_dev_inited) {
        AR_DBG(DM_NET_DEV, DL_ERR, "Ioctl net dev not inited!\n");
        spin_unlock_irqrestore(&handle->net_dev_opr_lock, flags);
        ret = -3;
        goto exit;
    }
    atomic_inc(&handle->net_dev_opr_ref_cnt);
    spin_unlock_irqrestore(&handle->net_dev_opr_lock, flags);

    if (!netif) {
        AR_DBG(DM_NET_DEV, DL_ERR, "%s: Net device is NULL!\n", __FUNCTION__);
        ret = -4;
        goto done;
    }

    priv = _ar_net_netif_lookup(handle, netif);
    if (!priv) {
        AR_DBG(DM_NET_DEV, DL_ERR, "%s: Net device priv is NULL!\n", __FUNCTION__);
        ret = -5;
        goto done;
    }

    skb_queue_tail(&priv->rx_pkt_list, skb);
    tasklet_hi_schedule(&priv->task);

done:
    atomic_dec(&handle->net_dev_opr_ref_cnt);
exit:
    AR_DBG(DM_NET_DEV, DL_INF, AR_FN_LEAVE_STR);

    return ret;
}

/**
 *  @brief Whether bb socket bind to netif with flag BB_SOCK_FLAG_DATAGRAM
 *
 *  @param handle   Refers to oal_handle.
 *  @param netif    Info of the net device(public)
 *
 *  @return         true:with flag BB_SOCK_FLAG_DATAGRAM, false:not with
 */
bool ar_net_is_datagram_socket(oal_handle *handle, ar_netif_t *netif)
{
    unsigned long flags;
    bool is_datagram = false;
    net_device_priv_t *priv = NULL;

    if (!handle) {
        is_datagram = false;
        goto exit;
    }


    spin_lock_irqsave(&handle->net_dev_opr_lock, flags);
    if (!handle->net_dev_inited) {
        spin_unlock_irqrestore(&handle->net_dev_opr_lock, flags);
        is_datagram = false;
        goto done;
    }
    atomic_inc(&handle->net_dev_opr_ref_cnt);
    spin_unlock_irqrestore(&handle->net_dev_opr_lock, flags);

    if (!netif) {
        AR_DBG(DM_NET_DEV, DL_ERR, "%s: Net device is NULL!\n", __FUNCTION__);
        is_datagram = false;
        goto done;
    }

    priv = _ar_net_netif_lookup(handle, netif);
    if (!priv) {
        AR_DBG(DM_NET_DEV, DL_ERR, "%s: Net device priv is NULL!\n", __FUNCTION__);
        is_datagram = false;
        goto done;
    }

    is_datagram = !!(priv->bb_socket_flag & BB_SOCK_FLAG_DATAGRAM);

done:
    atomic_dec(&handle->net_dev_opr_ref_cnt);

exit:
    return is_datagram;
}

/**
 *  @brief Whether netif has been create.
 *
 *  @param handle   Refers to oal_handle.
 *  @param netif    Info of the net device(public)
 *
 *  @return         true:created, false:not created
 */
bool ar_net_exists(oal_handle *handle, ar_netif_t *netif)
{
    unsigned long flags;
    bool net_exist = false;

    if (!handle) {
        net_exist = false;
        goto exit;
    }

    spin_lock_irqsave(&handle->net_dev_opr_lock, flags);
    if (!handle->net_dev_inited) {
        AR_DBG(DM_NET_DEV, DL_ERR, "Ioctl net dev not inited!\n");
        spin_unlock_irqrestore(&handle->net_dev_opr_lock, flags);
        net_exist = false;
        goto done;
    }
    atomic_inc(&handle->net_dev_opr_ref_cnt);
    spin_unlock_irqrestore(&handle->net_dev_opr_lock, flags);

    if(_ar_net_netif_lookup(handle, netif)) {
        net_exist = true;
    }
    else {
        net_exist = false;
    }

done:
    atomic_dec(&handle->net_dev_opr_ref_cnt);

exit:
    return net_exist;
}

/** TBD: register the stats get callback */
/**
 *  @brief Get stats of the net device
 *
 *  @param handle   Refers to oal_handle.
 *  @param netif    Info of the net device(public)
 *  @param stats    (OUT)stats of the net device
 *
 *  @return         0 for success, otherwise fail
 */
int ar_net_dev_stats_get(oal_handle *handle, ar_netif_t *netif, ar_netif_stats_t *stats)
{
    int ret = 0;
    unsigned long flags;
    net_device_priv_t *priv = NULL;

    if (!handle) {
        ret = -EINVAL;
        goto exit;
    }

    if (!stats) {
        ret = -EINVAL;
        goto exit;
    }

    if (!netif) {
        AR_DBG(DM_NET_DEV, DL_ERR, "%s: Net device is NULL!\n", __FUNCTION__);
        ret = -EINVAL;
        goto exit;
    }

    spin_lock_irqsave(&handle->net_dev_opr_lock, flags);
    if (!handle->net_dev_inited) {
        AR_DBG(DM_NET_DEV, DL_ERR, "Ioctl net dev not inited!\n");
        spin_unlock_irqrestore(&handle->net_dev_opr_lock, flags);
        return -EFAULT;
    }
    atomic_inc(&handle->net_dev_opr_ref_cnt);
    spin_unlock_irqrestore(&handle->net_dev_opr_lock, flags);

    priv = _ar_net_netif_lookup(handle, netif);
    if (!priv) {
        AR_DBG(DM_NET_DEV, DL_ERR, "%s: Net device priv is NULL!\n", __FUNCTION__);
        ret = -ENXIO;
        goto done;
    }

    memcpy(stats, &priv->ar_stats, sizeof(ar_netif_stats_t));

done:
    atomic_dec(&handle->net_dev_opr_ref_cnt);

exit:
    return ret;
}
